<?php
namespace Cola\Ext;

use Cola\Exception\DbException;

class Pdo
{
    /**
     * @var \PDO
     */
    public $pdo = null;

    /**
     * @var \PDOStatement
     */
    public $stmt = null;

    public $log = array();

    public $dsn;
    public $user;
    public $password;
    public $options;
    protected $transactionCount = 0;

    /**
     * Pdo constructor.
     *
     * @param string $dsn
     * @param string $user
     * @param string $password
     * @param array  $options
     *
     * @throws DbException
     */
    public function __construct($dsn, $user = '', $password = '', $options = array())
    {
        $options += array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8;', \PDO::ATTR_TIMEOUT => 3);
        $this->dsn = $dsn;
        $this->user = $user;
        $this->password = $password;
        $this->options = $options;
        $this->connect();
    }

    public function connect()
    {
    	try {
    		$this->pdo = new \PDO($this->dsn, $this->user, $this->password, $this->options);
    		return $this->pdo;
    	}
    	catch (\PDOException $e)
    	{
            throw new DbException($e, [$this->dsn,$this->user, $this->password, $this->options]);
    	}
    }

    /**
     * Close connection
     *
     */
    public function close()
    {
        $this->pdo = null;
    }

    /**
     * Free result
     *
     */
    public function free()
    {
        $this->stmt = null;
    }

    /**
     * Query sql
     *
     * @param string $sql
     * @param array  $data
     *
     * @return \PDOStatement
     * @throws DbException
     */
    public function query($sql, $data = array())
    {
        $startTime = microtime(true);
    	$this->log[] = $array = ['time' => date('Y-m-d H:i:s'), 'sql' => $sql, 'data' => $data];
        // 写入日志 只有开发环境才写日志，防止线上环境日志过多
        if(APP_DEV)
        {
            logfile('sql', $this->dsn.' ' .$sql.':'.json_encode($data));
        }
    	try {
			$this->error_info = '';
	        $this->stmt = $this->pdo->prepare($sql);
	        $res = $this->stmt->execute($data);
            $endTime = microtime(true);
            if(APP_DEV)
            {
                $executionTime = round(($endTime - $startTime), 3)*1000;
                $executionTime > 1000 && logfile('sql', $this->dsn.' slow_query:' .$executionTime.':'.$sql.':'.json_encode($data));
            }
        	return $this->stmt;
    	}
    	catch (\PDOException $e)
    	{
    		$this->error_info = $this->stmt->errorInfo();
    		if($this->getErrorInfo(true, $data) == 'reConnected')
    		{
    			return call_user_func(__METHOD__, $sql, $data);
    		}

            throw new DbException($e, $array);
    	}
    }

    /**
     * @param string $sql
     * @param array $data
     *
     * @return array|bool|mixed|\PDOStatement
     * @throws DbException
     *
     */
    public function sql($sql, $data = array())
    {
        $this->query($sql, $data);
        $tags = explode(' ', $sql, 2);
        switch (strtoupper($tags[0])) {
            case 'SELECT':
                $result = $this->fetchAll();
                break;
            case 'INSERT':
                $result = $this->lastInsertId();
                break;
            case 'UPDATE':
            case 'DELETE':
            case 'REPLACE':
                $result = (0 <= $this->affectedRows());
                break;
            default:
                $result = $this->stmt;
        }
        return $result;
    }

    /**
     * Get a result row
     *
     * @param string $sql
     * @param array  $data
     *
     * @return array
     */
    public function row($sql, $data = array())
    {
        $result = $this->sql($sql . ' limit 1', $data);
        return empty($result) ? [] : $result[0];
    }

    /**
     * Get first column of result
     *
     * @param string $sql
     * @param array  $data
     *
     * @return string
     */
    public function col($sql, $data = array())
    {
    	$result = $this->row($sql, $data);
        return current($result);
    }

    /**
     * Insert
     *
     * @param array  $data
     * @param string $table
     *
     * @return boolean
     * @throws DbException
     */
    public function insert($data, $table)
    {
        $keys = array();
        $marks = array();
        $values = array();
        foreach ($data as $key => $val) {
            is_array($val) && ($val = json_encode($val, JSON_UNESCAPED_UNICODE));
            $keys[] = "`{$key}`";
            $marks[] = '?';
            $values[] = $val;
        }

        $keys = implode(',', $keys);
        $marks = implode(',', $marks);
        $sql = "insert into {$table} ({$keys}) values ({$marks})";
        return $this->sql($sql, $values);
    }

    /**
     * Multiple insert
     *
     * @param array $rows
	 * @param string $table
     * @return boolean
     */
    public function minsert($rows, $table)
    {
        if (empty($rows)) {
            return true;
        }
        $bindOne = array_fill(0, count(current($rows)), '?');
        $bindAll = array_fill(0, count($rows), implode(',', $bindOne));
        $bind = '(' . implode('),(', $bindAll) . ')';
        $keys = array_keys(current($rows));
        $values = array();
        foreach ($rows as $row) {
            foreach ($keys as $key) {
                $value = is_array($row[$key]) ? json_encode($row[$key], JSON_UNESCAPED_UNICODE) : $row[$key];
                $values[] = $value;
            }
        }
        if (is_int($keys[0])) {
            $fields = '';
        } else {
            $fields = ' (`' . implode('`,`', $keys) . '`) ';
        }

        $sql = "insert into {$table}{$fields} values {$bind}";
        return $this->sql($sql, $values);
    }

    public function insertOnDuplicate($data, $replace, $table)
    {
        $keys = array();
        $marks = array();
        $values = array();
        foreach ($data as $key => $val) {
            is_array($val) && ($val = json_encode($val, JSON_UNESCAPED_UNICODE));
            $keys[] = "`{$key}`";
            $marks[] = '?';
            $values[] = $val;
        }

        $marks2 = array();
        foreach($replace as $key => $val) {
            is_array($val) && ($val = json_encode($val, JSON_UNESCAPED_UNICODE));
            $marks2[] = "`{$key}`=?";
            $values[] = $val;
        }

        $keys = implode(',', $keys);
        $marks = implode(',', $marks);
        $marks2 = implode(',', $marks2);
        $sql = "insert into {$table} ({$keys}) VALUES ({$marks}) on duplicate key update {$marks2}";
        return $this->sql($sql, $values);
    }

    /**
     * Replace
     *
     * @param array $data
     * @param string $table
     * @return boolean
     */
    public function replace($data, $table)
    {
        $keys = array();
        $marks = array();
        $values = array();
        foreach ($data as $key => $val) {
            is_array($val) && ($val = json_encode($val, JSON_UNESCAPED_UNICODE));
            $keys[] = "`{$key}`";
            $marks[] = '?';
            $values[] = $val;
        }

        $keys = implode(',', $keys);
        $marks = implode(',', $marks);
        $sql = "replace into {$table} ({$keys}) values ({$marks})";
        return $this->sql($sql, $values);
    }
	/**
     * 实现字段自增或自减
     *
	 * @param string $fields 字段名
     * @param number $value 正数或负数 
	 * @param array $where 条件
     * @param string $table 表名
     * @return boolean
     */
    public function setIncDec($fields, $value, $where = [], $table)
    {
        
		if (is_string($where)) {
            exit('no need string');
        }else{
			foreach($where as $key =>$val){
				 $keys[] = "`{$key}`=?";
				$values[] = $val;
			}
			$keys = implode(',', $keys);
		}
		$sql = "update {$table} set `$fields` = `$fields` + $value  where ({$keys})";
        return $this->sql($sql, $values);
    }
    /**
     * Multiple Replace
     *
     * @param array $rows
     * @param string $table
     * @return boolean
     */
    public function mreplace($rows, $table)
    {
        if (empty($rows)) {
            return true;
        }
        $bindOne = array_fill(0, count($rows[0]), '?');
        $bindAll = array_fill(0, count($rows), implode(',', $bindOne));
        $bind = '(' . implode('),(', $bindAll) . ')';
        $keys = array_keys($rows[0]);
        $values = array();
        foreach ($rows as $row) {
            foreach ($keys as $key) {
                $value = is_array($row[$key]) ? json_encode($row[$key], JSON_UNESCAPED_UNICODE) : $row[$key];
                $values[] = $value;
            }
        }
        if (is_int($keys[0])) {
            $fields = '';
        } else {
            $fields = ' (`' . implode('`,`', $keys) . '`) ';
        }

        $sql = "replace into {$table}{$fields} values {$bind}";
        return $this->sql($sql, $values);
    }

    /**
     * Update table
     *
     * @param array        $data
     * @param string|array $where
     * @param string       $table
     *
     * @return int
     * @throws DbException
     */
    public function update($data, $where = '0', $table)
    {
        $keys = array();
        $values = array();
        foreach ($data as $key => $val) {
            is_array($val) && ($val = json_encode($val, JSON_UNESCAPED_UNICODE));
            $keys[] = "`{$key}`=?";
            $values[] = $val;
        }
        $keys = implode(',', $keys);
        if (is_string($where)) {
            $where = array($where, array());
        }
        $values = array_merge($values, $where[1]);
        $sql = "update {$table} set {$keys} where {$where[0]}";
		//var_dump($sql);var_dump($values);die();
        return $this->sql($sql, $values);
    }

    /**
     * Delete from table
     *
     * @param string $where
	 * @param string $table
     * @return int
     */
    public function delete($where = '0', $table)
    {
        if (is_string($where)) {
            $where = array($where, array());
        }
        $sql = "delete from {$table} where {$where[0]}";
        return $this->sql($sql, $where[1]);
    }

    public function del($table, $where = '0')
    {
        return $this->delete($table, $where);
    }

    /**
     * Count num rows
     *
     * @param string $where
     * @param string $table
     * @return int
     */
    public function count($where = '1', $table)
    {
        if ( ! is_array($where)) {
            $where = [$where, []];
        }

        $sql = "select count(1) as cnt from {$table} where {$where[0]}";
        return intval($this->col($sql, $where[1]));
    }

    /**
     * Fetch one row result
     *
     * @param int $style
     *
     * @return mixed
     */
    public function fetch($style = \PDO::FETCH_ASSOC)
    {
        return $this->stmt->fetch($style);
    }

    /**
     * Fetch All result
     *
     * @param int $style
     *
     * @return array
     */
    public function fetchAll($style = \PDO::FETCH_ASSOC)
    {
        $result = $this->stmt->fetchAll($style);
        $this->free();
        return $result;
    }

    /**
     * Return the rows affected of the last sql
     *
     * @return int
     */
    public function affectedRows()
    {
        return $this->rowCount();
    }

    public function rowCount()
    {
        return $this->stmt->rowCount();
    }

    /**
     * Get the last inserted ID.
     *
     * @param string $name
     * @return mixed
     */
    public function lastInsertId($name = null)
    {
        $last = $this->pdo->lastInsertId($name);
        if (false === $last) {
            return false;
        } else if ('0' === $last) {
            return true;
        } else {
            return intval($last);
        }
    }

    /**
     * Ping server
     *
     * @param boolean $reconnect
     *
     * @return boolean
     * @throws DbException
     */
    public function ping($reconnect = true)
    {
    	try {
    		if ($this->pdo && $this->pdo->query('select 1')) {
    			return true;
    		}
    	}
    	catch (\PDOException $e)
    	{
    		if ($reconnect)
    		{
    			$this->close();
    			$this->connect();
    			return $this->ping(false);
    		}
    	}

        return false;
    }

    /**
     * 开启事务
     *
     * @return bool
     */
    public function beginTransaction()
    {
        if( ! $this->transactionCount++)
        {
            return $this->pdo->beginTransaction();
        }
        return $this->transactionCount >= 0;
    }

    /**
     * 提交事务
     *
     * @return bool
     */
    public function commit()
    {
        if( ! --$this->transactionCount)
        {
            return $this->pdo->commit();
        }

        return $this->transactionCount >= 0;
    }

    /**
     * 回滚事务
     *
     * @return bool
     */
    public function rollback()
    {
        $ret = false;
        if($this->transactionCount >= 0)
        {
            $ret = $this->pdo->rollBack();
        }

        $this->transactionCount = 0;
        return $ret;
    }

	//获取错误信息
    public function getErrorInfo($writeLog = true, $data = array())
    {
       // 如果是连接超时，则尝试重联 add by wang
    	if(isset($this->error_info[1]) && in_array($this->error_info[1], array('2013', '2006')))
        {
        	$this->ping(true);
        	return 'reConnected';
        }
        
    }
}